#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

#define LEFT "["
#define RIGHT "]"
#define DELIM " "
#define HISTORY "/home/gxq/linux/Shell/record"
enum DIR
{
    IN = 1,
    OUT,
    APPEND
};

char pwd[1024];
char *argv[32];
int exitcode;
char storenv[32];
int quit = 1;
char* filename;
int redirstatus;

const char *GetUser()
{
    return getenv("USER");
}

const char *GetHostName()
{
    return getenv("HOSTNAME");
}

void GetPwd()
{
    getcwd(pwd, sizeof(pwd));
}

void InputShell(char _command[], int sz)
{
    GetPwd();
    cout << LEFT << GetUser() << "@" << GetHostName() << " " << pwd << RIGHT << "#" << " ";

    fgets(_command, sz, stdin);

    int fd = open(HISTORY, O_CREAT | O_RDWR | O_APPEND, 0666);
    if (fd == -1)
    {
        perror("open fail");
        return;
    }
    write(fd, _command, sz);
    close(fd);

    _command[strlen(_command) - 1] = '\0';

    char* pos = _command;
    while(*pos)
    {
        if(*pos == '>')
        {
            if(*(pos + 1) == '>')
            {
                redirstatus = APPEND;
                *pos = '\0';
                pos += 2;
                while(isspace(*pos)) ++pos;
                filename = pos;
                break;
            }
            else
            {
                redirstatus = OUT;
                *pos = '\0';
                ++pos;
                while(isspace(*pos)) ++pos;
                filename = pos;
                break;
            }
        }
        else if(*pos == '<')
        {
            redirstatus = IN;
            *pos = '\0';
            ++pos;
            while(isspace(*pos)) ++pos;
            filename = pos;
            break;
        }

        ++pos;
    }
}

int InitArgv(char *_argv[], char _command[])
{
    int i = 0;
    _argv[i++] = strtok(_command, DELIM);
    while ((_argv[i - 1]) && (_argv[i++] = strtok(nullptr, DELIM)))
        ;
    int argc = i - 1;
    // if(argc == 0) continue;
    return argc;
}

void NormalCommand(char *_argv[])
{
    //重定向功能的实现
    int oflag = 0;
    int fd = 0;
    int recoverfd = 0;
    int changefd = 0;
    if(redirstatus == OUT)
    {
        changefd = 1;
        recoverfd = dup(1);
        fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
        dup2(fd,1);
        oflag = 1;
    }
    else if(redirstatus == APPEND)
    {
        changefd = 1;
        recoverfd = dup(1);
        fd = open(filename,O_CREAT|O_WRONLY|O_APPEND,0666);
        dup2(fd,1);
        oflag = 1;
    }
    else if(redirstatus == IN)
    {
        changefd = 0;
        recoverfd = dup(0);
        fd = open(filename,O_RDONLY);
        dup2(fd,0);
        oflag = 1;
    }

    // 处理history命令
    if (strcmp(argv[0], "history") == 0)
    {
        argv[0] = "cat";
        argv[1] = HISTORY;
        argv[2] = "-n";
        argv[3] = "-b";
        argv[4] = nullptr;
        // argc = 5;
    }

    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork fail");
        return;
    }
    else if (pid == 0)
    {
        // 子进程
        execvp(_argv[0], _argv);
        exit(11);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(pid, &status, 0);
        if (rid == pid)
        {
            exitcode = WEXITSTATUS(status);
        }
        else
        {
            perror("child exit error");
            return;
        }
    }

    //恢复 0或1 文件描述符
    if(oflag)
    {
        dup2(recoverfd,changefd);
        close(fd);
    }
}

int InnerCommand(int& _argc,char* _argv[])
{
    if(strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = nullptr;
        return 1;
    }

    if (_argc == 1 && strcmp(_argv[0], "exit") == 0)
    {
        quit = 0;
        return 0;
    }
    else if (_argc == 2 && strcmp(_argv[0], "cd") == 0)
    {
        chdir(_argv[1]);
        GetPwd();
        sprintf(getenv("PWD"), "%s", pwd);
        return 0;
    }
    else if (_argc == 2 && strcmp(_argv[0], "export") == 0)
    {
        strcpy(storenv, _argv[1]);
        putenv(storenv);
        return 0;
    }
    else if (_argc == 2 && strcmp(_argv[0], "echo") == 0)
    {
        if (strcmp(_argv[1], "$?") == 0)
        {
            cout << exitcode << endl;
            exitcode = 0;
        }
        else if (*_argv[1] == '$')
        {
            cout << getenv(_argv[1] + 1) << endl;
        }
        else
        {
            cout << _argv[1] << endl;
        }

        return 0;
    }

    return 1;
}

int main()
{
    while (quit)
    {
        redirstatus = 0;
        // 获取shell命令行的输入格式
        char command[1024] = {0};
        int sz = sizeof(command);
        InputShell(command, sz);

        // 切分命令
        int argc = InitArgv(argv, command);
        if (argc == 0)
            continue;

        // 处理内建命令
        int flag = 1;
        flag = InnerCommand(argc,argv);

        // 处理一般命令
        if (flag)
            NormalCommand(argv);
    }

    return 0;
}
